/*
 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.openjdk.btrace.runtime;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Jaroslav Bachorik
 */
public class Interval implements Comparable<Interval> {
    private static final Pattern INTERVAL_PATTERN = Pattern.compile("\\((\\d+)[,;]\\s*(\\d+)\\)");
    private static final Pattern COMP_PATTERN = Pattern.compile("(<|>|=|<=|>=|GT|LT|GE|LE|EQ)?(\\d+)");

    private int a, b;

    public Interval(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public static Interval eq(int value) {
        return new Interval(value, value);
    }

    public static Interval ge(int value) {
        return new Interval(value, Integer.MAX_VALUE);
    }

    public static Interval gt(int value) {
        return new Interval(value != Integer.MAX_VALUE ? value + 1 : Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    public static Interval le(int value) {
        return new Interval(Integer.MIN_VALUE, value);
    }

    public static Interval lt(int value) {
        return new Interval(Integer.MIN_VALUE, value != Integer.MIN_VALUE ? value - 1 : Integer.MIN_VALUE);
    }

    public static Interval all() {
        return new Interval(Integer.MIN_VALUE, Integer.MAX_VALUE);
    }

    public static Interval none() {
        return new Interval(Integer.MAX_VALUE, Integer.MIN_VALUE);
    }

    public static List<Interval> union(Collection<Interval> intervals) {
        Set<Interval> itvSet = new TreeSet<>(intervals);

        Iterator<Interval> iter = itvSet.iterator();
        Interval previous = null;
        while (iter.hasNext()) {
            if (previous == null) {
                previous = iter.next();
                continue;
            }
            Interval current = iter.next();
            if (current.a <= (previous.b != Integer.MAX_VALUE ? previous.b + 1 : Integer.MAX_VALUE)) {
                previous.b = current.b;
                iter.remove();
            } else {
                previous = current;
            }
        }
        return new ArrayList<>(itvSet);
    }

    public static List<Interval> invert(Collection<Interval> intervals) {
        Interval remainder = new Interval(Integer.MIN_VALUE, Integer.MAX_VALUE);
        Set<Interval> sorted = new TreeSet(union(intervals));
        List<Interval> result = new ArrayList<>();
        for (Interval i : sorted) {
            if (i.isAll()) {
                return Collections.singletonList(none());
            }
            if (i.a <= remainder.a) {
                if (i.b > remainder.a)
                    remainder.a = i.b != Integer.MAX_VALUE ? i.b + 1 : i.b;
            } else {
                result.add(new Interval(remainder.a, i.a - 1));
                if (i.b < remainder.b) {
                    remainder.a = i.b + 1;
                } else {
                    remainder = null;
                    break;
                }
            }
        }
        if (remainder != null) {
            result.add(remainder);
        }
        return result;
    }

    public static Interval fromString(String s) {
        Matcher m = INTERVAL_PATTERN.matcher(s);
        if (m.matches()) {
            int a = Integer.parseInt(m.group(1));
            int b = Integer.parseInt(m.group(2));
            return new Interval(a, b);
        } else {
            m = COMP_PATTERN.matcher(s);
            if (m.matches()) {
                String operator = m.group(1) != null ? m.group(1) : "";
                String val = m.group(2);
                int level = Integer.parseInt(val);
                switch (operator) {
                    case "EQ":
                    case "=": {
                        return eq(level);
                    }
                    case "LT":
                    case "<": {
                        return lt(level);
                    }
                    case "GT":
                    case ">": {
                        return gt(level);
                    }
                    case "LE":
                    case "<=": {
                        return le(level);
                    }
                    case "GE":
                    case ">=":
                    case "": {
                        return ge(level);
                    }
                    default: {
                        throw new IllegalArgumentException("Unrecognized operator: " + operator);
                    }
                }
            }
        }
        throw new IllegalArgumentException("Invalid level declaration: " + s);
    }

    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }

    public boolean isAll() {
        return a == Integer.MIN_VALUE && b == Integer.MAX_VALUE;
    }

    public boolean isNone() {
        return a == Integer.MAX_VALUE;
    }

    @Override
    public int compareTo(Interval o) {
        if (a < o.a) {
            return -1;
        } else if (a > o.a) {
            return 1;
        } else {
            if (b < o.b) {
                return -1;
            } else if (b > o.b) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 23 * hash + a;
        hash = 23 * hash + b;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        Interval other = (Interval) obj;
        if (a != other.a) {
            return false;
        }
        return b == other.b;
    }

    @Override
    public String toString() {
        if (a == Integer.MIN_VALUE) {
            if (b != Integer.MAX_VALUE) {
                return "LE" + b;
            }
        } else {
            if (b == Integer.MAX_VALUE) {
                return "GE" + a;
            }
        }
        return "(" + a + ";" + b + ")";
    }
}
